# -*- coding: utf-8 -*-

"""
@name: server
@author: Memory
@date: 2019/11/22
@document: 皇家战棋游戏的服务端文件
"""
import time
from threading import Thread
from twisted.internet.protocol import Protocol
from twisted.internet.protocol import Factory
from utils import *
from configs import *


class BCServerProtocol(Protocol):
    """服务端的协议，每一个用户连接进来都会建立这么一个协议，用来处理与该用户的通信"""
    def __init__(self, factory):
        self.factory = factory
        self.user = None
        self.id = -1
        self.last_action = time.time()
        self.log = Logging(SERVER_LOG_PATH)
        self.parse = {'signin': self.signin,
                      'signup': self.signup,
                      'match': self.match,
                      'unmatch': self.unmatch,
                      'endgame': self.endgame}

    @property
    def online(self):
        return len(self.factory.clients)

    @property
    def clients(self):
        return self.factory.clients

    @property
    def matched(self):
        return self.factory.matched

    @property
    def wait(self):
        return self.factory.wait

    def connectionMade(self):
        """
        建立连接时的动作。
        """
        self.factory.connection_num += 1
        self.factory.id += 1
        self.id = self.factory.id

    def connectionLost(self, reason):
        """
        失去连接时的操作。当主动调用 self.transport.loseConnection() 时也会触发这个函数
        """
        self.factory.connection_num -= 1
        if not self.user:
            return
        self.log.print(f"用户【{self.user}】失去了连接。")
        # 如果有正在进行的游戏，则判定为输
        if self.user in self.matched:
            data = {'type': 'giveup'}
            self.sendToMatched(dict2bin(data))
            v = self.cleangame()
            if v:
                self.log.print(f"因为【{self.user}】掉线，【{self.user}】和【{v}】的游戏结束!")
        elif self.user in self.wait:
            self.wait.remove(self.user)
            self.log.print(f"用户【{self.user}】放弃了匹配。")

        # 从客户池中删除，为了防止是同一用户不同地方登录，所以需要查看id
        if self.user in self.clients:
            client = self.clients[self.user]
            if client.id == self.id:
                self.clients.pop(self.user)

    def dataReceived(self, _data):
        """
        收到数据时的处理操作。
        """
        self.last_action = time.time()
        datas = spilt_data(_data)
        for data in datas:
            typ = data.type
            if typ in self.parse:
                self.parse[typ](data)
            else:
                self.log.print('用户 %s 进行了游戏操作: %s' % (self.user, typ))
                data["online"] = self.online
                self.sendToMatched(dict2bin(data))

    def signin(self, data):
        """
        处理用户的登录请求。
        """
        user = data.user
        self.log.print(f'用户【{user.name}】发起了登录请求。')
        # 查询是否存在该用户
        sql = 'SELECT passwd FROM user WHERE name=?'
        value = (user.name, )
        result = excuteSQL(sql, value)
        if result is None:      # 查询失败
            reply = {'type': 'signin', 'result': 'failed', 'reason': '系统出了一点问题。'}
        elif result == []:      # 查不到用户
            reply = {'type': 'signin', 'result': 'failed', 'reason': f'用户名【{user.name}】不存在。'}
        elif result[0][0] != user['passwd']:    # 密码不匹配
            reply = {'type': 'signin', 'result': 'failed', 'reason': '密码错误。'}
        else:                   # 存在该用户且密码正确
            # 如果其他地方已经登录，就踢掉对方
            if user.name in self.clients:
                self.clients[user.name].transport.loseConnection()
            # 添加到用户池
            self.clients[user.name] = self
            self.user = user.name
            reply = {'type': 'signin', 'user': get_user(user.name), 'result': 'success', 'online': self.online}

        # 打印日志
        if reply['result'] == 'failed':
            self.log.print(f"用户【{user.name}】登录失败。 因为： {reply['reason']}")
        else:
            self.log.print(f'用户【{user.name}】登录成功。')
        # 将结果发送给用户
        self.transport.write(dict2bin(reply))

    def signup(self, data):
        """
        处理用户的注册请求。
        """
        user = data.user
        self.log.print(f'用户 {user.name} 请求注册。')
        # 查询是否存在同名的用户
        sql = 'SELECT * FROM user WHERE name=?'
        value = (user.name, )
        result = excuteSQL(sql, value)
        if result is None:      # 查询失败
            reply = {'type': 'signup', 'result': 'failed', 'reason': '服务器出了一点问题。'}
        elif result == []:      # 查询成功，无同名用户
            # 将新用户添加到数据库
            sql = 'INSERT INTO user(name, passwd, credit, title) VALUES(?, ?, ?, ?)'
            value = (user.name, user.passwd, 0, '平民')
            if excuteSQL(sql, value) is not None:       # 插入成功，即注册成功
                reply = {'type': 'signup', 'name': user.name, 'result': 'success'}
            else:
                reply = {'type': 'signup', 'result': 'failed', 'reason': '服务器出了一点问题。'}
        else:
            reply = {'type': 'signup', 'result': 'failed', 'reason': f'用户名 {user.name} 已被注册。'}
        # 打印日志
        if reply['result'] == 'failed':
            self.log.print(f"用户【{user.name}】注册失败。 因为： {reply['reason']}")
        else:
            self.log.print(f'用户【{user.name}】注册成功。')
        # 将结果发送给用户
        self.transport.write(dict2bin(reply))

    def match(self, user):
        """
        用户请求匹配对手时，查看是否有等待的用户，如果有，就配对，如果没有，就等待。
        """
        self.log.print(f'用户【{user.name}】请求匹配游戏对手。')
        if user.name not in self.wait:
            self.wait.append(user.name)

        if len(self.wait) < 2:
            self.log.print(f'用户【{user.name}】正在等待匹配。')
        else:
            you = self.wait[0]
            me = self.wait[1]
            self.matched[me] = you
            self.matched[you] = me
            self.wait.remove(me)
            self.wait.remove(you)
            self.log.print(f'用户【{me}】和用户【{you}】匹配成功。')

            chess = random_chess()
            my_user = get_user(me)
            your_user = get_user(you)
            data1 = {'type': 'init', 'chess': chess, 'turn': 'red', 'color': 'red', 'me': my_user, 'you': your_user, 'online': self.online}
            data2 = {'type': 'init', 'chess': chess, 'turn': 'red', 'color': 'blue', 'me': your_user, 'you': my_user, 'online': self.online}
            self.transport.write(dict2bin(data1))
            self.sendToMatched(dict2bin(data2))

    def unmatch(self, user):
        """
        用户取消请求匹配对手时的操作。
        """
        if user.name in self.wait:
            self.wait.remove(user.name)
            self.log.print(f"用户【{user.name}】放弃了匹配。")

    def sendToMatched(self, data):
        """
        在游戏过程中，将游戏的数据包发送给对手
        """
        if self.user in self.matched:
            toid = self.matched[self.user]
            to = self.clients[toid]
            to.transport.write(data)

    def cleangame(self):
        if self.user in self.matched:
            v = self.matched[self.user]
            self.matched.pop(self.user)
            if v in self.matched:
                self.matched.pop(v)
            return v

    def endgame(self, data):
        """
        一局游戏结束时，玩家发送'endgame'数据包，服务端更新用户积分
        """
        v = self.cleangame()
        if v:
            self.log.print(f'用户【{self.user}】和【{v}】的游戏正常结束。')
        user = data.user
        if not user:
            return
        self.log.print('更新用户数据：', user)
        sql = 'UPDATE user SET credit=?, title=? WHERE name=?'
        value = (user['credit'], user['title'], user.name)
        excuteSQL(sql, value)


class BCServerFactory(Factory):
    """工厂类，处理所有的连接，每次用户连接，就使用一个BCServerProtocol类进行处理"""
    def __init__(self):
        self.connection_num = 0
        self.id = 0
        self.clients = {}
        self.matched = {}
        self.wait = []
        self.log = Logging(SERVER_LOG_PATH)
        thr = Thread(target=self.clean_client)
        thr.setDaemon(True)
        thr.start()
        self.log.print('启动服务器。')

    def buildProtocol(self, addr):
        return BCServerProtocol(self)

    def clean_client(self):
        while True:
            time.sleep(30)
            zombies = []
            for user, client in self.clients.items():
                not_move = time.time() - client.last_action
                if not_move > CLEAN_TIME:
                    zombies.append(user)

            for zombie in zombies:
                self.log.print(f"用户【{zombie}】太久没有操作，对其进行清理.")
                self.clients[zombie].transport.loseConnection()
